fix(core): one shared record-source object-name reader, six plugins delegate - #7637
Merged
Merged
Conversation
…elegate Six view plugins each spelled "the object this block is bound to" locally -- ObjectCalendar twice, ObjectGantt, ObjectTree twice, ObjectMap, ObjectGrid -- and had drifted: three wrote `?? schema.objectName`, one `|| ''`, one `: undefined`, one an `'object' in dataConfig` test. `@object-ui/core` now publishes `resolveRecordSourceObjectName`, which states the objectui#6939 record-source ladder once, and those sites delegate to it. Behaviour-neutral, measured rather than asserted: each site's pre-collapse expression is transcribed verbatim into `record-source.behaviourNeutrality-7627.test.ts` and asserted equal to its post-collapse spelling across the whole contract-valid input matrix. Two separately-ruled questions stay two. `normalizeListViewSchema`'s gap-fill (#7477, PR #7628 ruling B) is untouched and is not re-pointed at the new reader: it answers how `objectName` gets POPULATED when absent, where an already-present `objectName` wins. The new reader answers which object a block RESOLVES, where the `data` block wins -- the order declared on both published faces in `@object-ui/types`. `ObjectGantt`'s `persistLayoutKey` is excluded from the collapse and keeps its inverted order, with an in-place comment saying why: its receiver is a localStorage key (`gantt-layout:KEY:filters`), not a record source. `useSettledSchema`'s doc comment stops prescribing the hand-written ladder at all four lines that taught it. Fixes #7627 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
…tely omits
`ViewDataSchema` declares the `object` provider's `object` REQUIRED, so
`data: { provider: 'object' }` without one is off-contract -- but `ObjectGrid`
(via its old `'object' in dataConfig` test) and `ObjectTree`'s header label
coerced that shape back to `schema.objectName`, and ObjectGrid gates permission
verdicts with the result. The shared reader does not carry that coercion
(AGENTS.md #0.1); those two sites keep it as their own tail. Nothing pinned the
tails, so a later "redundant `??`" cleanup would have deleted them silently.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Collaborator
Landing — contract review PASS at
|
os-warren
deleted the
claude/issue-7627-object-provider-shared-reader
branch
September 4, 2026 14:09
This was referenced Sep 4, 2026
This was referenced Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7627
Implements option C as re-ruled in the retraction on the card (comment 5540047223). Cut from
origin/main11edab88; every line number re-measured there.@object-ui/corenow publishesresolveRecordSourceObjectName— one function stating the objectui#6939 record-source ladder once — and the seven record-source read sites delegate to it.What changed, site by site
ObjectCalendar:309schemaObjectNamecfg?.provider === 'object' ? cfg.object : schema.objectNameresolveRecordSourceObjectName(schema, dataConfig)ObjectCalendar:969overlayobjectNameObjectGantt:661resource... : schema.objectName ?? ''resolveRecordSourceObjectName(...) ?? ''ObjectTree:373schemaKey(...) ?? ''resolveRecordSourceObjectName(...) ?? ''ObjectTree:567headerObjectName(... : undefined) ?? schema.objectNameresolveRecordSourceObjectName(...) ?? schema.objectNameObjectMap:763-765metadataobjectNamedataProvider === 'object' ? dataObjectName : schema.objectNamerecordSourceObjectNameObjectGrid:1206objectName... && 'object' in cfg ? cfg.object : schema.objectNameresolveRecordSourceObjectName(...) ?? schema.objectNameuseSettledSchema's doc comment stopped prescribing the hand-written ladder at all four lines that taught it (55, 100, 111, 116) — 111 now shows the shared reader, and 116 is annotated as what it actually is: a provider test, not an object-name read, so gate placement stays local (objectui#6482) while the ladder does not.Behaviour-neutrality — measured, not asserted
packages/core/src/utils/__tests__/record-source.behaviourNeutrality-7627.test.tstranscribes each site's pre-collapse expression verbatim and asserts it equals its post-collapse spelling over a 13-shape input matrix — the five the dispatch named (both-bindings, data-only, objectName-only, empty-objectName, api-provider) plus api-without-name,value,staticDatawith and without a name, the array shorthand,data.object: '',objectName: ''alone, and nothing bound.0 divergences at all 7 delegating sites across the entire contract-valid matrix.
The one place before and after are not identical, stated plainly
ViewDataSchema'sobjectprovider is astrictObjectwithobjectrequired, sodata: { provider: 'object' }carrying noobjectis off-contract. Three sites used to answer that shape differently from each other — the drift this card was filed about:ObjectGrid:1206andObjectTree:567coerced it back toschema.objectName;ObjectCalendar/ObjectMap/ObjectGantt/ObjectTree:373resolved nothing.The shared reader carries no lenient rung for it (AGENTS.md #0.1 — a tolerant fallback in the renderer fossilizes a second de-facto contract). The two sites that coerced keep that coercion as their own tail at the site, each with a comment saying it is not the shared rung repeated. That keeps
ObjectGrid's permission gates (perms.canat 1224/1225/1236 — its own comment at 1283‑1287 calls that gate "the only one on that shape") resolving exactly what they resolve today. The tails are pinned by their own tests, so a later "redundant??" cleanup goes red instead of silent.Residual, one site, off-contract only:
ObjectGantt:661resourcegoes fromundefinedto''on that shape, because??binds tighter than?:so the old line applied its empty-string floor to the fallback arm only.resourceis declaredstring; every consumer tests!resourceorobjectName === resource, so no branch moves — only the literal first argument tofind/update/deleteon a schema the published validator rejects.Two questions stay two questions
normalizeListViewSchema's gap-fill (#7477, PR #7628 ruling B) is untouched and is not re-pointed at the new reader. It answers howobjectNamegets populated when absent, where an already-presentobjectNamewins. The new reader answers which object a block resolves, where thedatablock wins — declared on both published faces in@object-ui/typesand pinned byobjectql-record-source-refinement-6939.test.ts. Merging them would silently override one standing ruling or the other; ruling B's own words — "it can never re-point a binding that already resolves" — argue for keeping them apart, because at these sites the binding that already resolves isdata.object.Excluded from the collapse
ObjectGantt:1374— a localStorage key (gantt-layout:KEY:filters). Left exactly as it is, with an in-place comment: re-pointing it silently orphans saved layouts and filter chips for any view carrying both bindings. Its "disagreement" with:661is resolved by the ruling that they were never answering the same question.ObjectGantt:1897— a liveness predicate ('object'OR'api') deciding whether a refresh handler exists. Not an object-name reader.plugin-dashboard/utils.ts:15isObjectProvider— an exported type-guard over a widget'sdata; never sees a schema.ObjectTree:445andObjectMap:686— the data config's own object, deliberatelyundefinedfor other providers so anapi/valueview'sobjectNamechanging cannot move an effect dependency. Both now carry a comment saying why they do not delegate.Verification (all from the repo root)
Union re-run on final head
6e640f4f:pnpm exec vitest run packages/core/ packages/plugin-calendar/ packages/plugin-gantt/ packages/plugin-tree/ packages/plugin-map/ packages/plugin-grid/ packages/react/—Test Files 410 passed (410),Tests 5014 passed (5014), lockVERDICT command-exit 0pnpm exec turbo run type-check --concurrency=2(repo-wide) —Tasks: 81 successful, 81 total, lockVERDICT command-exit 0pnpm exec eslintover the 9 changed TS/TSX files — exit 0, 0 errors; the two new files carry 0 warnings; no newreact-hooks/exhaustive-depswarning from theObjectMapdependency additioncheck-phantom-dependencies"✅ Every in-scope import is declared by the package that publishes it" ·check-readme-exports"✅ … 421 self-imports judged (421 real, 0 wrong-path, 0 fabricated)" ·check-changeset-presence"✅ 7 source file(s) of 7 released package(s) changed, and this change declares 1 changeset(s)" ·check-changeset-no-major"✅ No changeset declares amajorbump" ·check-changeset-fixed·check-changeset-overwrite·check-control-bytes·check-package-self-import·check-side-effects-array·check-entry-guard·check-element-data-source-declaration·check-spec-symbol-derivation·check-vi-mock-specifiers·check-vi-mock-inherit·check-lint-coverage·check-type-check-coverage·check-dist-completeness·check-node-esm-load·check-published-dist-tooling— all exit 0check-eager-closure-budgetandcheck-sdui-registration-pinsneed anapps/consolebuild and report NOT MEASURED locally ("broken gauge, not 4 budgets that all passed"); both weigh the console bundle, which this diff does not touch. CI decides them.Ablation
Each rung of the reader cut in turn; mutation proved on disk by anchored counts and blob hash, restore proved by blob-hash equality and an empty
git diff HEAD, with atrap … EXIT INT TERMholding an absolute path. Vitest aliases@object-ui/coretopackages/core/src, so both legs take effect without a rebuild.101 passedschema.objectName, i.e. "objectName wins")Leg A's one-off is my under-count, not a surprise in the code: I predicted the "no lenient rung" fact would fail once and it is asserted in two places. Leg B matched exactly, including the prediction that
ObjectTree:567andObjectGrid:1206survive rung-2 loss on their own tails while the other five sites go red — independent confirmation that those tails are load-bearing.Declared narrowing: the ablation's three vitest invocations ran outside the shared verify lock, narrowed to that one pure-TypeScript unit file with
--maxWorkers=1, after ~20 minutes of queue timeouts (exit 99, NOT MEASURED) with no turn. No build, no DOM, seconds of CPU. Every other build/test run in this PR went through the lock and quotes itsVERDICTline.Generated by Claude Code